﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
using Caliburn.Micro;
using System.Linq;
using RatingAuthor;
using SamizdatSearch.ConditionTree;

namespace ConditionTreeTestStand.Nodes
{
  public abstract class NodeViewModel : PropertyChangedBase, IDraggableTreeNode
  {
    protected NodeContext NodeContext { get; private set; }

    #region ' ResultsCount Property (change notification) '

    public int ResultsCount
    {
      get { return _resultsCount; }
      set
      {
        if (_resultsCount == value) return;
        _resultsCount = value;
        NotifyOfPropertyChange(() => ResultsCount);
      }
    }

    private int _resultsCount;

    #endregion

    #region ' Public Properties '

    #region ' Operator Property (change notification) '

    public Operator Operator
    {
      get { return _operator; }
      set
      {
        if (_operator == value) return;
        _operator = value;
        OnOperatorChanged();
        NotifyOfPropertyChange(() => Operator);
      }
    }

    private Operator _operator;

    #endregion

    #region ' OperatorPossibleValues Property (change notification) '

    public IEnumerable<OperatorViewModel> OperatorPossibleValues
    {
      get { return _operatorPossibleValues; }
      set
      {
        if (_operatorPossibleValues == value) return;
        _operatorPossibleValues = value;
        NotifyOfPropertyChange(() => OperatorPossibleValues);
      }
    }

    private IEnumerable<OperatorViewModel> _operatorPossibleValues;

    #endregion

    public NodeViewModel Parent { get; internal set; }
    public ObservableCollection<NodeViewModel> Children { get; private set; }

    public virtual bool CanAddGroupChild { get { return true; } }
    public virtual bool CanAddConditionChild { get { return true; } }
    public virtual bool IsAddParentVisible { get { return true; } }
    public virtual bool CanSwitchNodeType { get { return true; } }
    public virtual bool CanRemove
    {
      get
      {
        if (Parent is RootNodeViewModel)
          if (Children.Count == 0 || Children.Any(c => c is ConditionNodeViewModel))
            return false;
        return true;
      }
    }

    #endregion

    protected NodeViewModel(NodeViewModel parent, NodeContext context)
    {
      Parent = parent;
      NodeContext = context;
      Children = new ObservableCollection<NodeViewModel>();
      if (parent != null) parent.Children.Add(this);

      Operator = Operator.GetOperators(OperandType.Bool).First();
      OperatorPossibleValues = Operator.
        GetOperators(OperandType.Bool).
        Select(o => new OperatorViewModel(this, o));

    }

    #region ' Public Methods '

    public void AddGroupChild()
    {
      new GroupNodeViewModel(this, NodeContext);
      OnChildrenChanged();
    }
    public void AddConditionChild()
    {
      new ConditionNodeViewModel(this, NodeContext);
      OnChildrenChanged();
    }
    public void AddParent()
    {
      var oldParent = Parent;
      var oldParentChildren = oldParent.Children;
      var idx = oldParentChildren.IndexOf(this);
      oldParentChildren.RemoveAt(idx);
      var newNode = new GroupNodeViewModel(null, NodeContext);
      oldParentChildren.Insert(idx, newNode);
      newNode.Children.Add(this);
      newNode.Parent = Parent;
      Parent = newNode;
      OnParentChanged();
      oldParent.OnChildrenChanged();
    }
    public void SwitchNodeType()
    {
      var replacement = CreateReplacementNode();

      var idx = Parent.Children.IndexOf(this);
      Parent.Children[idx] = replacement;
      Parent.OnChildrenChanged();

      foreach (var child in Children)
      {
        replacement.Children.Add(child);
        child.Parent = replacement;
      }

      replacement.Parent = Parent;
    }
    public void Remove()
    {
      Parent.RemoveChild(this);
    }
    public virtual void RemoveChild(NodeViewModel child)
    {
      Children.Remove(child);
      foreach (var grand in child.Children)
      {
        Children.Insert(0, grand);
        grand.Parent = this;
      }
      OnChildrenChanged();
    }
    public abstract Expression GetExpression();
    public abstract bool TestBook(Book b);

    #endregion

    #region ' Implementation '

    protected abstract NodeViewModel CreateReplacementNode();

    #endregion

    #region ' Change Notification '

    protected virtual void OnParentChanged()
    {
      NotifyOfPropertyChange(() => CanRemove);
      OnConditionChanged();
    }
    protected virtual void OnChildrenChanged()
    {
      NotifyOfPropertyChange(() => CanRemove);
      OnConditionChanged();
    }
    protected internal virtual void OnConditionChanged()
    {
      if (Parent != null) 
        Parent.OnConditionChanged();
    }
    protected virtual void OnOperatorChanged()
    {
      OnConditionChanged();
    }

    #endregion


    public bool CanHaveChildren
    {
      get { return this is GroupNodeViewModel; }
    }

    public int Index
    {
      get { return Parent.Children.IndexOf(this); }
    }

    public bool CanDrop(IDraggableTreeNode targetNode)
    {
      if (targetNode == null) return false;
      var t = (NodeViewModel)targetNode;
      while (t != null && t != this) t = t.Parent;
      return t != this;
    }

    public void Drop(IDraggableTreeNode target, int index)
    {
      var targetNode = target as NodeViewModel;
      if (targetNode == null) throw new ArgumentOutOfRangeException("target");
      if (targetNode == Parent)
      {
        if (Index == index) return;
        if (Index <= index)
          index--;
      }
      Parent.Children.Remove(this);
      if (index == -1)
      {
        targetNode.Children.Add(this);
      }
      else
      {
        targetNode.Children.Insert(index, this);
      }
      Parent = targetNode;
    }
  }
}